【SwiftUI】UIHostingControllerのdismissのアニメーションを無効化にする
UIHositingController
でラップしたSwiftUIのView
を画面破棄する時のアニメーションを無効化にする方法を調べたので記載します。
環境
- Xcode 14
はじめに
今回はUIViewController
のViewController
からSwiftUIのView
のSwiftUIView
をUIHostingController
でラップしたものを表示して試しました。
最初の画面(ViewController)
Storyboard
コード
import UIKit import SwiftUI class ViewController: UIViewController { // ボタンを押すと、UIHostingControllerでラップしたSwiftUIViewに遷移します @IBAction private func presentNextView() { let swiftUIViewController = UIHostingController(rootView: SwiftUIView()) swiftUIViewController.modalPresentationStyle = .overFullScreen present(swiftUIViewController, animated: false) } }
次の画面(SwiftUIView)
プレビュー
コード
import SwiftUI struct SwiftUIView: View { @Environment(\.dismiss) var dismiss var body: some View { ZStack { Rectangle() .fill(.black) .ignoresSafeArea() Button { var transaction = Transaction() transaction.disablesAnimations = true withTransaction(transaction) { dismiss() } } label: { Text("dismiss") } } } }
トランザクションを使用して画面破棄のアニメーションを無効化にする(失敗)
SwiftUIのViewから.sheet
等で表示させたViewを画面破棄する際にアニメーションを無効化させるには、withTransaction
メソッドにTransaction
を使用してアニメーションを無効化にしたカスタムアニメーションを渡すことで画面破棄のアニメーションを無効化に出きます。
var transaction = Transaction() transaction.disablesAnimations = true withTransaction(transaction) { dismiss() }
しかし、この方法だとUIHositingController
でラップしたSwiftUIのView
を画面破棄する時のアニメーションを無効化に出来ませんでした。
デモ
しっかり画面破棄のアニメーションが表示されています。
推測
前回の背景色を透明にする時でもそうだったのですが、UIHositingController
を使用する場合は、そのラップをしているUIHositingController
側のデフォルト値が変更出来ておらず、変更が反映できていないのではないかと推測しました。
解決策
親側のViewControllerで画面破棄の処理を行う
SwiftUIView
画面破棄を行いたい箇所をdismiss()
から、dismissHandler()
に変更しています。
struct SwiftUIView: View { let dismissHandler: () -> Void var body: some View { ZStack { Rectangle() .fill(.black) .ignoresSafeArea() Button { dismissHandler() } label: { Text("dismiss") } } } }
ViewController
UIHostingController
のrootViewとしてSwiftUIView
を渡す際にdismissHandler
内でアニメーションを実行しないdismiss
を実行するようにしました。
import UIKit import SwiftUI class ViewController: UIViewController { @IBAction private func presentNextView() { let swiftUIViewController = UIHostingController(rootView: SwiftUIView(dismissHandler: { self.dismiss(animated: false) })) swiftUIViewController.modalPresentationStyle = .overFullScreen present(swiftUIViewController, animated: false) } }
デモ
無事にアニメーションを無効化に出来ました。
UIViewControllerTransitioningDelegateでアニメーションをカスタマイズする
上記の方法でdismiss
のアニメーションの無効化に成功したのですが、別の方法も試しました。
UIViewControllerTransitioningDelegate
に準拠させることで遷移アニメーションをカスタマイズ出来ます。また、UIHostingController
はUIViewController
を継承しているので、UIViewControllerTransitioningDelegate
に準拠させることが出来ます。
今回はSwiftUIView
をラップしたSwiftUIViewController
にUIViewControllerTransitioningDelegate
を準拠させました。
SwiftUIViewController
import UIKit import SwiftUI class SwiftUIViewController: UIHostingController<SwiftUIView> { init() { super.init(rootView: SwiftUIView()) self.transitioningDelegate = self } @MainActor required dynamic init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } // MARK: - UIViewControllerTransitionDeleate extension SwiftUIViewController: UIViewControllerTransitioningDelegate { func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return AnimatedTransition() } } class AnimatedTransition: NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { transitionContext.completeTransition(true) } }
まず、init()
実行時にtransitioningDelegate
にself
を渡します。
init() { super.init(rootView: SwiftUIView()) self.transitioningDelegate = self }
UIViewControllerTransitionDeleate
のanimationController(forDismissed:)
メソッドを呼び出して、画面破棄時のUIViewControllerAnimatedTransitioning
に準拠したアニメーターオブジェクトを返すように要求します。
// MARK: - UIViewControllerTransitionDeleate extension SwiftUIViewController: UIViewControllerTransitioningDelegate { func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return AnimatedTransition() } }
アニメータオブジェクトの、transitionDuration(using:)
で画面遷移の時間間隔を設定します。今回はアニメーションを無しにしたい為、0
にしています。
animateTransition(using:)
では、実行したカスタムアニメーションを記述するのですが、今回は特にアニメーションは必要ない為、completeTransition(true)
でアニメーションが終了したことのみを伝えるようにしています。
class AnimatedTransition: NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { transitionContext.completeTransition(true) } }
デモ
この方法でも無事に画面破棄時のアニメーションを無効化にすることが出来ました!
おわりに
SwiftUIのView
をUIViewController
と合わせて使用することもあると思いますが、UIHostingController
でラップしたView
が意図した動きをしない時はUIHostingController
側の設定が変わっていない可能性もある為、UIHostingController
の設定変更を試してみるのも良いかもしれませんね。
他に何か良い方法がありましたら教えていただければと思います。